#include "raster.h"
#include "raster-fixed.h"
#include "raster-channels.h"
#include "raster-triangles.h"
#include "raster-rects.h"
#include <math.h>
#include <stdlib.h>

Raster * __cdecl CreateRaster(unsigned int fmt, unsigned int width, unsigned int height)
{
    Raster *ret = (Raster *)malloc(sizeof(Raster));
    if(ret)
    {
        ret->fmt = fmt;
        ret->pitch = ret->width = width;
        ret->height = height;
        ret->rasterTargetData = NULL;
        ret->pixels = (Pixel *)malloc(ret->pitch * ret->height * 4);
        ret->clipRect.x = ret->clipRect.y = 0;
        ret->clipRect.w = ret->width;
        ret->clipRect.h = ret->height;
        if(!ret->pixels)
        {
            free(ret);
            ret = NULL;
        }
    }

    return ret;
}

void __cdecl DestroyRaster(Raster *raster)
{
    free(raster->pixels);
    free(raster);
}

BEST_INLINE void _SequentialApplyOpaque(Pixel *first, Pixel *last, Pixel color)
{
    while(first < last)
    {
        *first = color;
        ++first;
    }
}

BEST_INLINE void _SequentialApplyAlpha(Pixel *first, Pixel *last, Pixel color)
{
    while(first < last)
    {
        *first = AlphaBlendPixels(*first, color);
        ++first;
    }
}

int __cdecl RasterFillRect(Raster *raster, Pixel color, const RasterRect *pRect)
{
    unsigned int wy, ex, ey;
    RasterRect rect = *pRect;
    Pixel *pfirst, *plast;
    /* clipping */
    if(!RectSanityCheck(pRect))
        return ERROR_INVALID_INPUT;
    ClipRect(&raster->clipRect, &rect);
    if(!RectSanityCheck(&rect))
        return 0; /* nothing to do */

    wy = rect.y;
    ex = (rect.x + rect.w);
    ey = (rect.y + rect.h);
    /* sanity check */
    if(ex >= raster->pitch)
        ex = raster->pitch;
    if(ey >= raster->height)
        ey = raster->height;

    pfirst = raster->pixels + (wy * raster->pitch) + rect.x;
    plast = raster->pixels + (wy * raster->pitch) + ex;
    if(IsPixelOpaque(color))
    {
        while(wy < ey)
        {
            _SequentialApplyOpaque(pfirst, plast, color);
            pfirst += raster->pitch;
            plast += raster->pitch;
            wy++;
        }
    }
    else /* not opaque */
    {
        while(wy < ey)
        {
            _SequentialApplyAlpha(pfirst, plast, color);
            pfirst += raster->pitch;
            plast += raster->pitch;
            wy++;
        }
    }

    return 0;
}

int __cdecl RasterClear(Raster *raster, Pixel color)
{
    _SequentialApplyOpaque(raster->pixels, raster->pixels + (raster->pitch * raster->height), color);
    return 0;
}

int __cdecl RasterDrawLine(Raster *raster, Pixel color, const RasterPos *pt1, const RasterPos *pt2)
{
    RasterVert p1 = VertFromPos(pt1->x, pt1->y);
    RasterVert p2 = VertFromPos(pt2->x, pt2->y);
    Edge edge;
    Fixed lineSegWidth;
    int stopy = raster->clipRect.y + raster->clipRect.h;
    void (*fillFn)(Pixel *, Pixel *, Pixel) = NULL;

    PrepareEdge(&edge, &p1, &p2);
    edge.x += FIXED_HALF; /* rounding */
    if(edge.dx >= 0 && edge.dx < FIXED_ONE)
        lineSegWidth = FIXED_ONE;
    else if(edge.dx < 0 && edge.dx > -FIXED_ONE)
        lineSegWidth = -FIXED_ONE;
    else
        lineSegWidth = edge.dx;

    stopy = IMIN(stopy, edge.y + edge.h);

    /* vertical clipping */
    if(edge.y < raster->clipRect.y)
        EdgeSkip(&edge, raster->clipRect.y - edge.y);
    if(edge.y >= stopy)
        return 0; /* nothing to do */

    if(IsPixelOpaque(color))
        fillFn = _SequentialApplyOpaque;
    else
        fillFn = _SequentialApplyAlpha;

    do
    {
        int x0 = FixedToInt(edge.x);
        int x1 = FixedToInt(edge.x + lineSegWidth);
        Pixel *line = raster->pixels + (edge.y * raster->pitch);

        if(x0 <= x1)
        {
            ClipSpan(&raster->clipRect, &x0, &x1);
            fillFn(line + x0, line + x1, color);
        }
        else
        {
            ClipSpan(&raster->clipRect, &x1, &x0);
            fillFn(line + x1, line + x0, color);
        }
        EdgeCrawl(&edge);
    } while(edge.y < stopy);
    return 0;
}

static void PrepareTriangleEdges(Edge *edges, RasterVert *verts)
{
    Edge _swap;
    PrepareEdge(&edges[0], &verts[0], &verts[1]);
    PrepareEdge(&edges[1], &verts[0], &verts[2]);
    PrepareEdge(&edges[2], &verts[1], &verts[2]);

    /* ensure edges[0] and edges[1] start at the top */
    if(edges[0].y > edges[1].y)
    {
        _swap = edges[0];
        edges[0] = edges[1];
        edges[1] = _swap;
    }
    if(edges[0].y > edges[2].y)
    {
        _swap = edges[0];
        edges[0] = edges[2];
        edges[2] = _swap;
    }
    if(edges[1].y > edges[2].y)
    {
        _swap = edges[1];
        edges[1] = edges[2];
        edges[2] = _swap;
    }
}

static int __cdecl _SpansBetweenTriangleOpaque(Raster *raster, Edge *edge0, Edge *edge1, void *args)
{
    int x0 = FixedToInt(edge0->x);
    int x1 = FixedToInt(edge1->x);
    Pixel *line = raster->pixels + (edge0->y * raster->pitch);
    Pixel color = *(Pixel *)(args);

    if(x0 <= x1)
    {
        if(x0 == x1) x1++;
        ClipSpan(&raster->clipRect, &x0, &x1);
        _SequentialApplyOpaque(line + x0, line + x1, color);
    }
    else
    {
        ClipSpan(&raster->clipRect, &x1, &x0);
        _SequentialApplyOpaque(line + x1, line + x0, color);
    }
    return 0;
}
static int __cdecl _SpansBetweenTriangleAlpha(Raster *raster, Edge *edge0, Edge *edge1, void *args)
{
    int x0 = FixedToInt(edge0->x);
    int x1 = FixedToInt(edge1->x);
    Pixel *line = raster->pixels + (edge0->y * raster->pitch);
    Pixel color = *(Pixel *)(args);

    if(x0 <= x1)
    {
        if(x0 == x1) x1++;
        ClipSpan(&raster->clipRect, &x0, &x1);
        _SequentialApplyAlpha(line + x0, line + x1, color);
    }
    else
    {
        ClipSpan(&raster->clipRect, &x1, &x0);
        _SequentialApplyAlpha(line + x1, line + x0, color);
    }
    return 0;
}

int __cdecl RasterDrawTriangle(Raster *raster, Pixel color, const RasterPos *pts)
{
    RasterVert verts[3] = { VertFromPos(pts[0].x, pts[0].y),
                            VertFromPos(pts[1].x, pts[1].y),
                            VertFromPos(pts[2].x, pts[2].y)
                            };
    Edge edges[3];
    SpanFn fn;

    PrepareTriangleEdges(edges, verts);

    if(IsPixelOpaque(color))
        fn = _SpansBetweenTriangleOpaque;
    else
        fn = _SpansBetweenTriangleAlpha;

    SpansBetween(raster, &edges[0], &edges[1], fn, &color);
    if(edges[0].y == edges[2].y) /* can draw final edge too */
    {
        if(edges[0].h != 0)
            SpansBetween(raster, &edges[0], &edges[2], fn, &color);
        else
            SpansBetween(raster, &edges[1], &edges[2], fn, &color);
    }

    return 0;
}

int __cdecl RasterBlit(Raster *dest, const RasterPos *pos, Raster *src, const RasterRect *srcRect, BlitMode *mode)
{
    int lineBlitRet = 0;
    int y = 0;
    const RasterFormat fmt = LookupFormat(src->fmt);
    RasterRect sRect = *srcRect;
    RasterRect dRect = {pos->x, pos->y, srcRect->w, srcRect->h };

    if(dest->fmt > FORMAT_TARGET_ALPHA)
    {
        return ERROR_INVALID_INPUT;
    }

    if(!RectSanityCheck(srcRect))
        return ERROR_INVALID_INPUT;
    ClipBlitRects(&dest->clipRect, &dRect, &sRect);
    if(!RectSanityCheck(&dRect) || !RectSanityCheck(&sRect))
        return 0; /* nothing to do */

    while(y < sRect.h && lineBlitRet == 0)
    {
        lineBlitRet = fmt.LineBlit(dest, src, sRect.w, (const RasterPos *)&dRect, (const RasterPos *)&sRect, mode);
        dRect.y++;
        sRect.y++;
        y++;
    }
    return lineBlitRet;
}

int __cdecl RasterStretchBlit(Raster *dest, const RasterRect *destRect, Raster *src, const RasterRect *srcRect, BlitMode *mode)
{
    RasterRect dRect = *destRect;
    RasterRect sRect = *srcRect;
    const RasterFormat fmt = LookupFormat(src->fmt);
    int y = 0;
    int lineBlitRet = 0;
    RasterPosUV uvLeft, uvRight;
    Fixed vIter;

    if(dest->fmt > FORMAT_TARGET_ALPHA)
    {
        return ERROR_INVALID_INPUT;
    }

    if(!RectSanityCheck(srcRect) || !RectSanityCheck(destRect))
        return ERROR_INVALID_INPUT;
    ClipBlitRects(&dest->clipRect, &dRect, &sRect);
    if(!RectSanityCheck(&dRect) || !RectSanityCheck(&sRect))
        return 0; /* nothing to do */

    /* subtract 1 from width and height because zero index */
    vIter = IntToFixed(sRect.h - 1) / IMAX(1, dRect.h - 1);
    uvLeft.u = IntToFixed(sRect.x);
    uvLeft.v = IntToFixed(sRect.y);
    uvRight.u = IntToFixed(sRect.x + sRect.w - 1);
    uvRight.v = IntToFixed(sRect.y);

    while(y < dRect.h && lineBlitRet == 0)
    {
        lineBlitRet = fmt.LineBlitUV(dest, src, dRect.w, (const RasterPos *)&dRect, &uvLeft, &uvRight, mode);
        uvLeft.v += vIter;
        uvRight.v += vIter;
        dRect.y++;
        y++;
    }
    return lineBlitRet;
}

struct TriangleBlitArgs
{
    const RasterFormat *fmt;
    Raster *src;
    BlitMode *mode;
};

int __cdecl _SpansBetweenTriangleBlit(Raster *dest, Edge *edge0, Edge *edge1, void *args)
{
    RasterPosUV uv0 = { edge0->u, edge0->v };
    RasterPosUV uv1 = { edge1->u, edge1->v };
    RasterPos p0 = { FixedToInt(edge0->x), edge0->y };
    RasterPos p1 = { FixedToInt(edge1->x), edge1->y };
    struct TriangleBlitArgs *arg = (struct TriangleBlitArgs *)args;
    int lineBlitRet = 0;

    if(p0.x <= p1.x)
    {
        if(p0.x == p1.x) p1.x++;
        ClipSpanUV(&dest->clipRect, &p0.x, &p1.x, &uv0, &uv1);
        lineBlitRet = arg->fmt->LineBlitUV(dest, arg->src, p1.x - p0.x, &p0, &uv0, &uv1, arg->mode);
    }
    else
    {
        ClipSpanUV(&dest->clipRect, &p1.x, &p0.x, &uv1, &uv0);
        lineBlitRet = arg->fmt->LineBlitUV(dest, arg->src, p0.x - p1.x, &p1, &uv1, &uv0, arg->mode);
    }
    return lineBlitRet;
}

int __cdecl RasterTriangleBlit(Raster *dest, const RasterPos *destPts, Raster *src, const RasterPos *srcPts, BlitMode *mode)
{
    RasterVert verts[3] = { VertFromPosTex(destPts[0].x, destPts[0].y, srcPts[0].x, srcPts[0].y),
                            VertFromPosTex(destPts[1].x, destPts[1].y, srcPts[1].x, srcPts[1].y),
                            VertFromPosTex(destPts[2].x, destPts[2].y, srcPts[2].x, srcPts[2].y)
                            };
    Edge edges[3];
    struct TriangleBlitArgs args;
    const RasterFormat fmt = LookupFormat(src->fmt);

    PrepareTriangleEdges(edges, verts);

    args.fmt = &fmt;
    args.src = src;
    args.mode = mode;

    SpansBetween(dest, &edges[0], &edges[1], _SpansBetweenTriangleBlit, &args);
    if(edges[0].y == edges[2].y) /* can draw final edge too */
    {
        if(edges[0].h != 0)
            SpansBetween(dest, &edges[0], &edges[2], _SpansBetweenTriangleBlit, &args);
        else
            SpansBetween(dest, &edges[1], &edges[2], _SpansBetweenTriangleBlit, &args);
    }

    return 0;
}

static void _PrepareQuadEdges(Edge *edges, RasterVert *verts)
{
    Edge _swap;
    PrepareEdge(&edges[0], &verts[0], &verts[1]);
    PrepareEdge(&edges[1], &verts[1], &verts[2]);
    PrepareEdge(&edges[2], &verts[2], &verts[3]);
    PrepareEdge(&edges[3], &verts[3], &verts[0]);

    /* ensure edges[0] and edges[1] start at the top */
    if(edges[0].y > edges[1].y)
    {
        _swap = edges[0];
        edges[0] = edges[1];
        edges[1] = _swap;
    }
    if(edges[0].y > edges[2].y)
    {
        _swap = edges[0];
        edges[0] = edges[2];
        edges[2] = _swap;
    }
    if(edges[0].y > edges[3].y)
    {
        _swap = edges[0];
        edges[0] = edges[3];
        edges[3] = _swap;
    }
    if(edges[1].y > edges[2].y)
    {
        _swap = edges[1];
        edges[1] = edges[2];
        edges[2] = _swap;
    }
    if(edges[1].y > edges[3].y)
    {
        _swap = edges[1];
        edges[1] = edges[3];
        edges[3] = _swap;
    }
    if(edges[2].y > edges[3].y)
    {
        _swap = edges[2];
        edges[2] = edges[3];
        edges[3] = _swap;
    }
}

int __cdecl RasterQuadBlit(Raster *dest, const RasterPos *destPts, Raster *src, const RasterPos *srcPts, BlitMode *mode)
{
    RasterVert verts[4] = { VertFromPosTex(destPts[0].x, destPts[0].y, srcPts[0].x, srcPts[0].y),
                            VertFromPosTex(destPts[1].x, destPts[1].y, srcPts[1].x, srcPts[1].y),
                            VertFromPosTex(destPts[2].x, destPts[2].y, srcPts[2].x, srcPts[2].y),
                            VertFromPosTex(destPts[3].x, destPts[3].y, srcPts[3].x, srcPts[3].y)
                            };
    Edge edges[4];
    struct TriangleBlitArgs args;
    const RasterFormat fmt = LookupFormat(src->fmt);

    _PrepareQuadEdges(edges, verts);

    args.fmt = &fmt;
    args.src = src;
    args.mode = mode;

    SpansBetween(dest, &edges[0], &edges[1], _SpansBetweenTriangleBlit, &args);
    if(edges[0].y == edges[2].y) /* can draw third edge too */
    {
        if(edges[0].h != 0)
            SpansBetween(dest, &edges[0], &edges[2], _SpansBetweenTriangleBlit, &args);
        else
            SpansBetween(dest, &edges[1], &edges[2], _SpansBetweenTriangleBlit, &args);
    }
    if(edges[2].y == edges[3].y) /* can draw third edge too */
    {
        if(edges[0].h != 0)
            SpansBetween(dest, &edges[0], &edges[3], _SpansBetweenTriangleBlit, &args);
        else if(edges[1].h != 0)
            SpansBetween(dest, &edges[1], &edges[3], _SpansBetweenTriangleBlit, &args);
        else
            SpansBetween(dest, &edges[2], &edges[3], _SpansBetweenTriangleBlit, &args);
    }

    return 0;
}
